package nebenlaeufig;

import java.util.ArrayList;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;


public class NLastPartition {
	
	ExecutorService pool;
	
	int workingThread;
	

	Lock todoLock = new ReentrantLock(),
		fertigLock = new ReentrantLock(),
		allLock = new ReentrantLock(),
		workingLock = new ReentrantLock(),
		finaleLock = new ReentrantLock();
	
	Condition full = workingLock.newCondition();

	Queue<NMesh> todo = new LinkedBlockingQueue<NMesh>();
	Queue<NMesh> fertig = new LinkedBlockingQueue<NMesh>();
	Queue<NMesh> all = new LinkedBlockingQueue<NMesh>();
	boolean ausgabe;
	boolean lockausgabe;
	
	boolean finale;
	
	public Queue<NMesh> getTodo(){
		return todo;
	}
	
	public Queue<NMesh> getAll(){
		return all;
	}
	/**
	 * gets a initial list of meshes and calculate the smallest meshes
	 * @param firstPartition initial list of meshes
	 * @param ausgabe	output on/off
	 * @param lockausgabe	lock output on/off
	 * @throws InterruptedException 
	 */
	public NLastPartition(ArrayList<NMesh> firstPartition, boolean ausgabe,boolean lockausgabe) throws InterruptedException{
		this.ausgabe=ausgabe;
		this.lockausgabe=lockausgabe;
		int cores = Runtime.getRuntime().availableProcessors();
		cores = cores+1;
		pool = Executors.newFixedThreadPool(cores);
		
		NMesh actual = null;
		for(int i=0;i<firstPartition.size();i++){
			actual = firstPartition.get(i);
			if(actual.size()==1)
				fertig.add(actual);
			else
				todo.add(actual);
			all.add(actual);
		}
		
		finale = false;
		workingThread = 0;
		
		while(true){
			while(todo.size()!=0){
				if(ausgabe)
					System.out.println("Verwalter Todo.size: " + todo.size());
				try{
					workingLock.lock();
					while(workingThread>=cores)
						full.await();
					workingThread++;
					if(ausgabe)
						System.out.println("Verwalter: WorkingThread:" +workingThread);
				}finally{
					workingLock.unlock();
				}
				
				pool.execute(thread);
			}
			try{
				todoLock.lock();
				if(workingThread==0){
					if(todo.size()==0){
						if(finale)
							break;
						finale = true;
						int size = fertig.size();
						NMesh act;
						for(int i=0;i<size;i++){
							act = fertig.poll();
							if(act.size()!=1)
								todo.add(act);
							else 
								fertig.add(act);
						}
						if(ausgabe)
						System.out.println("nochmal "+ todo.size());
					}
				}
			}finally{
				todoLock.unlock();
			}
			try{
				workingLock.lock();
				while(workingThread!=0 && todo.size()==0)
					full.await();
			}finally{
				workingLock.unlock();
			}
		}
		
		pool.shutdown();
		
		if(ausgabe)
			System.out.println("Verwalter fertig");
	}
	
	public Queue<NMesh> getFertig(){
		return fertig;
	}
	
	Runnable thread = new Runnable(){
		/**
		 * takes one element of todo-list and tries to split it
		 */
		@Override
		public void run() {
												int id=0;
												String leer="";
												if(ausgabe){
													id=Character.getNumericValue(Thread.currentThread().getName().charAt(Thread.currentThread().getName().length()-1));
													for(int i=0;i<id;i++)
														leer += "--------------------";
													System.out.println(leer +Thread.currentThread().getName()+": Start");
												}
			//to say that there is a thread working
			ArrayList<NMesh> allcopy = new ArrayList<NMesh>();
			try{
				allLock.lock();
												if(lockausgabe)
													System.out.println(leer +Thread.currentThread().getName()+": --Alllock");
				for(NMesh m : all)
					allcopy.add(m);
					
			}finally{
				allLock.unlock();
												if(lockausgabe)
													System.out.println(leer +Thread.currentThread().getName()+": --Allunlock");
			}
			ArrayList<NMesh> newMeshes = new ArrayList<NMesh>();
			NMesh[] erg = new NMesh[2];
			NMesh cur = null;
			boolean split = false;
			//take the first mesh m from todo list
			try{
				todoLock.lock();
												if(lockausgabe)
													System.out.println(leer +Thread.currentThread().getName()+": --Todolock");
				if(todo.size()>0)
					cur = todo.poll();
												if(ausgabe){
													System.out.println(leer +Thread.currentThread().getName()+": "+cur+" mit ");
//													if(cur!=null)
//													for(int i=0;i<allcopy.size();i++)
//														System.out.println(leer +Thread.currentThread().getName()+"-(" + allcopy.get(i)+")-");
												}
			}finally{
				todoLock.unlock();
												if(lockausgabe)
													System.out.println(leer +Thread.currentThread().getName()+": --Todounlock");
			};
			if(cur!=null){
				//if m has only one node, it cannot be splitted
				if(cur.size()==1){
					try{
						fertigLock.lock();
												if(lockausgabe)
													System.out.println(leer +Thread.currentThread().getName()+": --Fertiglock");
						fertig.add(cur);
					}finally{
						fertigLock.unlock();
												if(lockausgabe)
													System.out.println(leer +Thread.currentThread().getName()+": --Fertigunlock");
					}
				}
				else{
					//iterate over all meshes and check if it can split m
					for(int i=0;i<allcopy.size();i++){
						erg = split(cur,allcopy.get(i));
						if(erg!=null){
							newMeshes.add(erg[0]);
							newMeshes.add(erg[1]);
							if(ausgabe){
								System.out.println(leer +Thread.currentThread().getName()+": newMeshes: ("+newMeshes.get(0)+")("+newMeshes.get(1)+")");
								System.out.println(leer +Thread.currentThread().getName()+": gesplittet mit "+allcopy.get(i));
							}
							split = true;
							try{
								finaleLock.lock();
								finale = false;
							}finally{
								finaleLock.unlock();
							}
							break;
						}
					}
					//if m wasn't splitted, it is ready
					if(!split){
						try{
							fertigLock.lock();
												if(lockausgabe)
													System.out.println(leer +Thread.currentThread().getName()+": --Fertig2lock");
							fertig.add(cur);
						}finally{
							fertigLock.unlock();
												if(lockausgabe)
													System.out.println(leer +Thread.currentThread().getName()+": --Fertig2unlock");
						}
					}
					//else there could be another mesh in the fertig list which could be splitted by the new meshes
					else{
						try{
							allLock.lock();
												if(lockausgabe)
													System.out.println(leer +Thread.currentThread().getName()+": --All2lock");
							all.remove(cur);
							if(lockausgabe)
								System.out.println(leer +Thread.currentThread().getName()+": --All3lock");
							for(int i=0;i<newMeshes.size();i++)
								all.add(newMeshes.get(i));
						}finally{
							allLock.unlock();
												if(lockausgabe)
													System.out.println(leer +Thread.currentThread().getName()+": --All2unlock");
						}
						//so copy all from fertig back to todo
						Queue<NMesh> fertigCopy = new ConcurrentLinkedQueue<NMesh>();
						try{
							fertigLock.lock();
												if(lockausgabe)
													System.out.println(leer +Thread.currentThread().getName()+": --Fertig3lock");
							int size = fertig.size();
							NMesh act;
							for(int i=0;i<size;i++){
								act = fertig.poll();
								if(act.size()!=1)
									fertigCopy.add(act);
								else
									fertig.add(act);
							}
								
						}finally{
							fertigLock.unlock();
												if(lockausgabe)
													System.out.println(leer +Thread.currentThread().getName()+": --Fertig3unlock");
						}
						try{
							todoLock.lock();
												if(lockausgabe)
													System.out.println(leer +Thread.currentThread().getName()+": --Todo2lock");
							while(fertigCopy.size()!=0)
								todo.add(fertigCopy.poll());
												if(ausgabe)
													System.out.println(leer +Thread.currentThread().getName()+": "+fertigCopy.size()+" zu Todo von Fertig");
						//and add the new meshes in todo
							for(int i=0;i<newMeshes.size();i++)
								todo.add(newMeshes.get(i));
						}finally{
							todoLock.unlock();
												if(lockausgabe)
													System.out.println(leer +Thread.currentThread().getName()+": --Todo2unlock");
						}
					}
				}
			}
			//to say, that this thread is now ready
			try{
				workingLock.lock();
												if(lockausgabe)
													System.out.println(leer +Thread.currentThread().getName()+": --Working2lock");
				workingThread--;
				//to awake the main thread
				full.signalAll();
				if(ausgabe)
					System.out.println(leer +Thread.currentThread().getName()+": WorkingThread: "+workingThread);
			}finally{
				workingLock.unlock();
												if(lockausgabe)
													System.out.println(leer +Thread.currentThread().getName()+": --Working2unlock");
			}
												if(ausgabe)
													System.out.println(leer +Thread.currentThread().getName()+": Stopp");
		}
	};
	
	/**
	 * gets two Meshes and splits m with mi into two new meshes
	 * @param m
	 * @param mi
	 * @return	two new meshes
	 */
	private NMesh[] split(NMesh m, NMesh mi){
		NMesh pre = NGraph.Pre(mi);
		NMesh[] newMesh = new NMesh[2];
		newMesh[0]=new NMesh();
		newMesh[1]=new NMesh();
		boolean check= false;
		boolean check2= true;
		
		if(pre.equal(m)) //if pre contains exactly of all elements of m
			return null;
		if(pre.size()==0) //if pre is emty
			return null;
		for(int i=0;i<m.size();i++) //if m without pre != empty set
			if(!pre.contains(m.get(i))){
				check=true;
				break;
			}
		if(!check)
			return null;
		for(int i=0;i<pre.size();i++) //if pre doesn't contains any element of m
			if(m.contains(pre.get(i))){
				check2=false;
				break;
			}
		if(check2)
			return null;
		for(int i=0;i<m.size();i++)
		{
			if(pre.contains(m.get(i)))
				newMesh[0].add(m.get(i));
			if(!pre.contains(m.get(i)))
				newMesh[1].add(m.get(i));
		}
		return newMesh;
	}
}